﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;


namespace Boilen.Primitives.Members {

    /// <summary>
    /// Writes code for a method declaration.
    /// </summary>
    public sealed class MethodMember : HelpedMember {

        private readonly List<ParameterMember> parameters_ = new List<ParameterMember>( );
        private readonly List<Doc> docDependencies_ = new List<Doc>( );
        private readonly BlockMember body_;


        /// <inheritdoc/>
        public override Extensions UsedExtensions {
            get {
                return base.UsedExtensions
                     | (this.Body ?? Member.Empty).UsedExtensions
                     | Util.GetUsedExtensions( this.Parameters );
            }
        }

        /// <summary>
        /// Gets a value indicating whether this is a partial method.
        /// </summary>
        public bool IsPartialMethod { get { return this.Body == null; } }

        /// <summary>
        /// Gets the list of parameters for the method.
        /// </summary>
        public IList<ParameterMember> Parameters { get { return this.parameters_; } }

        /// <summary>
        /// Gets the list of additional Doc values to apply as setting dependencies.
        /// </summary>
        public IList<Doc> DocDependencies { get { return this.docDependencies_; } }

        /// <summary>
        /// Gets the body of the method, or <null/> if it is a partial method.
        /// </summary>
        public BlockMember Body { get { return this.body_; } }


        /// <summary>
        /// Initializes a new <see cref="MethodMember"/> instance.
        /// </summary>
        /// <param name="name">The name of the method.</param>
        /// <param name="returnType">The return type of the method.</param>
        /// <param name="body">The body of the method.</param>
        public MethodMember( string name, string returnType, BlockMember body )
            : base( name, returnType, !IsConstructor( body ), "public" ) {
            this.body_ = body;

            this.SpaceOutput = true;
        }


        /// <summary>
        /// Adds the specified parameters to the <see cref="Parameters"/> list.
        /// </summary>
        public MethodMember AddParameters( IEnumerable<ParameterMember> parameters ) {
            this.parameters_.AddRange( parameters );
            return this;
        }

        /// <summary>
        /// Constructs and adds <see cref="ParameterMember"/>s based on the parameters of the existing method.
        /// </summary>
        public static IEnumerable<ParameterMember> GetParameters( MethodBase existingMethod, Func<string, Type, string, ParameterMember> createParameter ) {
            Ensure.NotNull( existingMethod, createParameter );

            var existingParameters = existingMethod.GetParameters( );
            var parameterDocs = existingParameters.Select( p => p.Name ).ToDictionary( n => n, n => "" );

            try {
                var methodDoc = XmlDocumentation.GetDocMember( existingMethod );
                parameterDocs = methodDoc.Elements( "param" ).ToDictionary(
                    paramDoc => paramDoc.Attribute( "name" ).Value,
                    paramdDoc => XmlDocumentation.GetElementValue( paramdDoc )
                );
            }
            catch( Exception e ) {
                string errorMessage = string.Format( "Could not get documentation for existing method {0}:{1}{2}", existingMethod, Environment.NewLine, e );
                System.Diagnostics.Debug.WriteLine( errorMessage );
            }

            foreach( var parameter in existingParameters ) {
                string name = parameter.Name;
                yield return createParameter( name, parameter.ParameterType, parameterDocs[name] );
            }
        }


        /// <inheritdoc/>
        protected override void WriteDoc( ICodeWriter writer ) {
            if( this.Doc != null ) {
                this.Doc.UpdateSettings( this.Parameters.Select( p => p.Doc ) );
                this.Doc.UpdateSettings( this.DocDependencies );
            }

            base.WriteDoc( writer );
            if( !this.IsPartialMethod )
                Member.WriteMemberDocumentation( writer, this.Parameters );
        }

        /// <inheritdoc/>
        protected override void WriteCore( ICodeWriter writer ) {
            string type = this.Type + (this.Type.Length > 0 ? " " : "");
            string modifiers = this.IsPartialMethod
                ? (this.Modifiers.Contains( "static" ) ? "static " : "") + "partial"
                : this.Modifiers;
            writer.Write( "{0} {1}{2}", modifiers, type, this.Name );

            using( Enclose.Parenthesis( writer ) ) {
                WriteMembers( writer, this.Parameters, ( i, last ) => writer.Write( ", " ) );
            }

            if( this.IsPartialMethod ) {
                writer.WriteLine( ";" );
            }
            else {
                writer.WriteLine( );
                this.Body.Write( writer );
            }
        }

        /// <inheritdoc/>
        protected override void OnDocChanged( ) {
            base.OnDocChanged( );

            if( this.Doc != null )
                this.Doc.AddExceptions( this.Body.Guards );
        }


        private static bool IsConstructor( BlockMember body ) {
            string bodyName = body != null
                ? body.Name.Trim( )
                : null;
            return bodyName != null
                && (bodyName[0] == '.' || bodyName[0] == ':');
        }

    }

}
